package com.spzx.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.spzx.cart.api.RemoteCartService;
import com.spzx.cart.api.domain.CartInfo;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.context.SecurityContextHolder;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.DateUtils;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.rabbit.constant.MqConst;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.order.domain.OrderInfo;
import com.spzx.order.domain.OrderItem;
import com.spzx.order.domain.OrderLog;
import com.spzx.order.form.OrderForm;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.order.service.IOrderInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.order.vo.TradeVo;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.RemoteSkuStockService;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.SkuPriceVo;
import com.spzx.product.vo.SkuLockVo;
import com.spzx.user.api.RemoteUserAddressService;
import com.spzx.user.api.domain.UserAddress;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author gax
 * @since 2025-09-29
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {

    // 购物车的远程调用 @FeignClient
    @Autowired
    private RemoteCartService remoteCartService;

    // 商品的远程调用 @FeignClient
    @Autowired
    private RemoteProductService remoteProductService;

    // 用户地址的远程调用 @FeignClient
    @Autowired
    private RemoteUserAddressService remoteUserAddressService;

    // 商品库存信息的远程调用 @FeignClient
    @Autowired
    private RemoteSkuStockService remoteSkuStockService;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private OrderLogMapper orderLogMapper;

    // 展示订单结算页面
    @Override
    public TradeVo trade() {
        // 远程调用购物车微服务，获取选中的商品列表，封装订单明细列表
        R<List<CartInfo>> cartCheckedList = remoteCartService.getCartCheckedList(SecurityConstants.INNER);

        if (cartCheckedList.getCode() == R.FAIL) {
            throw new ServiceException("远程调用购物车服务失败，原因：" + cartCheckedList.getMsg());
        }
        List<CartInfo> cartInfoList = cartCheckedList.getData();
        // 创建订单结算 vo 对象
        TradeVo tradeVo = new TradeVo();
        // 将购物车商品列表转为订单明细列表
        if (CollectionUtils.isEmpty(cartInfoList)) {
            throw new ServiceException("没有需要结算的商品!");
        }
        // 将购物车商品转为订单明细
        List<OrderItem> orderItemList = cartInfoList.stream().map(elm -> {
            OrderItem orderItem = new OrderItem();
            BeanUtils.copyProperties(elm, orderItem);
            return orderItem;
        }).toList();

        tradeVo.setOrderItemList(orderItemList);

        // 计算订单的总金额
        BigDecimal totalAmount = new BigDecimal(0);
        for (OrderItem orderItem : orderItemList) {
            totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
        }
        tradeVo.setTotalAmount(totalAmount);

        // 生成本次订单流水号，防止重复点击提交订单，将流水号存入 Redis
        String tradeNo = this.generateTradeNo();
        tradeVo.setTradeNo(tradeNo);
        return tradeVo;
    }

    // 展示订单结算页面时，生成流水号，保存到 Redis
    @Override
    public String generateTradeNo() {
        // Redis 的 key
        String tradeKey = "user:tradeNo:" + SecurityContextHolder.getUserId();
        // Redis 的 value，定义为流水号
        String tradeNo = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(tradeKey, tradeNo, 5, TimeUnit.MINUTES);
        // 返回流水号
        return tradeNo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long submitOrder(OrderForm orderForm) {
        // ① 业务校验，验证流水号，防止订单重复提交或长时间停留在订单结算页面
        String tradeKey = "user:tradeNo:" + SecurityContextHolder.getUserId();
        // 通过 lua 脚本，验证流水号
        // KEYS[1]: 表示流水号 key；ARGV[1] 表示用户提交的流水号
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" + "then\n" +
                " return redis.call(\"del\",KEYS[1])\n" +
                "else\n" + " return 0\n" + "end\n";
        // 执行 lua 脚本
        DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>(script, Boolean.class);
        Boolean flag = (Boolean) redisTemplate.execute(defaultRedisScript, Arrays.asList(tradeKey), orderForm.getTradeNo());
        if (!flag) {
            // 如果用户点击了多次提交订单，实则只会校验第一次的提交订单，第一次校验时，因为有流水号机制，redis.call("del",KEYS[1])
            // 就将 redis 中的 key 给删除掉了，之后的流水号校验都会失败，走到这里
            throw new ServiceException("流水号校验失败，请重新提交!");
        }

        // ② 验证订单中明细商品价格是否变更
        // 根据订单明细列表中 skuID 列表，远程调用 "商品服务"，得到实时价格列表
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        List<Long> skuIdList = orderItemList.stream().map(orderItem -> orderItem.getSkuId()).toList();
        // 远程调用 "商品微服务"，获取最新的价格列表
        R<List<SkuPriceVo>> r = remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
        if (R.FAIL == r.getCode()) {
            throw new ServiceException("远程调用商品微服务失败，原因：" + r.getMsg());
        }
        List<SkuPriceVo> skuPriceVoList = r.getData();
        // 为方便后续处理，将 List 变换为 Map
        // List 中每个元素是一个 vo 对象，想得到这样的一个 Map：key 为 vo 中的 skuId 属性，value 为 vo 中的售价属性
        LinkedHashMap<Long, BigDecimal> skuPriceMap = skuPriceVoList.stream().collect(Collectors.toMap(
                SkuPriceVo::getSkuId,
                SkuPriceVo::getSalePrice,
                // 预防有重复的 skuId，提供下合并逻辑，冲突时取新的
                (oldV, newV) -> newV,
                // 保证 Map 的键顺序与原 List 的遇到顺序一致,故使用 LinkedHashMap
                LinkedHashMap::new
        ));


        // 判断商品价格是否有变更，如果有变更，远程调用 “购物车微服务”，更新购物车中的商品价格，并且提示价格有变，请重新结算!
        // 引导用户去购物车页面重新结算即可
        StringBuilder sb = new StringBuilder();

        for (OrderItem orderItem : orderItemList) {
            // 使用 BigDecimal 的 compareTo 去比较价格是否有变换，如果 compareTo 的结果不为 0，则说明两者不一样
            if (orderItem.getSkuPrice().compareTo(skuPriceMap.get(orderItem.getSkuId())) != 0) {
                sb.append(orderItem.getSkuName()).append("\n");
            }
        }

        if (sb.length() > 0) {
            // 订单微服务中远程调用购物车微服务，去更新购物车中商品的价格
            remoteCartService.updateCartPrice(SecurityContextHolder.getUserId(),SecurityConstants.INNER);
            // 如果商品价格有变更，则抛出异常，需要重新提交订单
            throw new ServiceException(sb.append("以上商品有价格变动!").toString());
        }

        // ③ 如果商品价格没有变更，则保存订单以及明细，记录日志
        OrderInfo orderInfo = this.saveOrder(orderForm);

        // TODO 锁定商品库存，基于 RabbitMQ 异步锁定库存
        List<SkuLockVo> skuLockVoList = orderItemList.stream().map(orderItem -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            BeanUtils.copyProperties(orderItem, skuLockVo);
            return skuLockVo;
        }).toList();
        R<String> r1 = remoteSkuStockService.checkAndLock(orderInfo.getOrderNo(), skuLockVoList, SecurityConstants.INNER);
        if (R.FAIL == r1.getCode()) {
            // 注意：我们定义的 ServiceException 本质是 RuntimeException 运行时异常
            // 由于添加了 @Transactional(rollbackFor = Exception.class)
            // 所以抛出异常可以事务回滚
            throw new ServiceException("库存锁定失败了!");
        }

        String stockErrorMsg = r1.getData();
        if (StringUtils.isNotBlank(stockErrorMsg)) {
            throw new ServiceException(stockErrorMsg);
        }

        // 保存订单信息成功后，只有通过购物车途径来到提交订单页面时，才需要去清空购物车
        // 反之，如果是在页面直接点击的立即购买，这种清空是无需清理购物车的
        if (!orderForm.getIsBuy()) {
            remoteCartService.deleteCartCheckedList(SecurityConstants.INNER);
        }
        // 响应订单，对接支付页面
        return orderInfo.getId();
    }

    @Override
    public OrderInfo saveOrder(OrderForm orderForm) {
        // 创建订单对象
        OrderInfo orderInfo = new OrderInfo();
        // ① 设置用户信息
        Long userId = SecurityContextHolder.getUserId();
        String userName = SecurityContextHolder.getUserName();
        orderInfo.setUserId(userId);
        orderInfo.setNickName(userName);
        // ② 设置订单编号
        // 生成订单编号，形式：日期 ＋ 全局唯一 ID（雪花算法）
        // IdWorker 为 MybatisPlus 框架提供的获取雪花算法的类
        String orderNo = DateUtils.dateTime() + IdWorker.getIdStr();
        orderInfo.setOrderNo(orderNo);
        // ③ 设置订单的总金额
        // 遍历订单明细列表，计算订单总金额
        BigDecimal totalAmount = orderForm.getOrderItemList().stream()
                // 将每个订单项映射成它的单项金额（这里取的是单价 skuPrice）
                .map(OrderItem::getSkuPrice)
                // 从 0 开始，把所有 BigDecimal 依次相加，得到合计
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        orderInfo.setTotalAmount(totalAmount);
        orderInfo.setOriginalTotalAmount(totalAmount);
        // ④ 设置订单的状态：默认的初始状态是未付款，对应 0
        orderInfo.setOrderStatus(0);
        // ⑤ 设置订单的创始人
        orderInfo.setCreateBy(userName);
        // ⑥ 设置订单的备注
        orderInfo.setRemark(orderForm.getRemark());
        // ⑦ 设置收件人信息
        // 远程调用 "用户微服务"，根据地址的 ID，获取地址信息
        R<UserAddress> r = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
        if (R.FAIL == r.getCode()) {
            throw new ServiceException("远程调用用户微服务失败，原因：" + r.getMsg());
        }
        UserAddress userAddress = r.getData();
        orderInfo.setReceiverName(userAddress.getName());
        orderInfo.setReceiverPhone(userAddress.getPhone());
        orderInfo.setReceiverTagName(userAddress.getTagName());
        orderInfo.setReceiverProvince(userAddress.getProvinceCode());
        orderInfo.setReceiverCity(userAddress.getCityCode());
        orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
        orderInfo.setReceiverAddress(userAddress.getAddress());
        // 使用 MP 持久化保存订单信息
        this.save(orderInfo);

        // ⑧ 保存订单明细信息 ---> 保存 order_item 表的订单相关信息
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        if (!CollectionUtils.isEmpty(orderItemList)) {
            for (OrderItem orderItem : orderItemList) {
                orderItem.setOrderId(orderInfo.getId());
                orderItem.setCreateBy(userName);
                orderItemMapper.insert(orderItem);
            }
        }
        // ⑨ 保存订单操作日志
        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderInfo.getId());
        orderLog.setOperateUser(userId.toString());
        orderLog.setProcessStatus(0);
        orderLog.setNote(orderForm.getRemark());
        orderLog.setCreateBy(userId.toString());
        orderLogMapper.insert(orderLog);
        return orderInfo;
    }

    @Override
    public TradeVo buy(Long skuId) {
        // 创建订单确认 Vo 对象
        TradeVo tradeVo = new TradeVo();
        // 封装订单明细
        // 远程调用 "商品微服务"，查询商品信息
        R<ProductSku> r = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);
        if (R.FAIL == r.getCode()) {
            throw new ServiceException("远程调用商品微服务失败！原因是：" + r.getMsg());
        }
        ProductSku productSku = r.getData();
        // 给订单明细列表做封装
        OrderItem orderItem = new OrderItem();
        // 明细列表中只需要封装 skuId，skuName，缩略图，skuPrice，skuNum
        orderItem.setSkuId(skuId);
        orderItem.setSkuName(productSku.getSkuName());
        orderItem.setThumbImg(productSku.getThumbImg());
        orderItem.setSkuPrice(productSku.getSalePrice());
        // 立即购买时，默认就立即购买一件
        orderItem.setSkuNum(1);
        List<OrderItem> orderItemList = Arrays.asList(orderItem);
        tradeVo.setOrderItemList(orderItemList);
        // 封装订单总金额
        tradeVo.setTotalAmount(orderItem.getSkuPrice());
        // 生成流水号
        String tradeNo = this.generateTradeNo();
        tradeVo.setTradeNo(tradeNo);
        // 设置立即购买为：true
        tradeVo.setIsBuy(true);
        return tradeVo;
    }

    @Override
    public List<OrderInfo> userOrderInfoList(Long userId, String orderStatus) {
        // 查询订单列表
        // 根据用户的 id，查询出用户的订单列表
        LambdaQueryWrapper<OrderInfo> queryWrapper
                = new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getUserId, userId)
                .eq(!StringUtils.isEmpty(orderStatus), OrderInfo::getOrderStatus, orderStatus);
        List<OrderInfo> orderInfoList = this.list(queryWrapper);
        if (!CollectionUtils.isEmpty(orderInfoList)) {
            // 将订单列表转为订单 ID 列表 orderIdList
            List<Long> orderIdList = orderInfoList.stream().map(OrderInfo::getId).toList();
            // 查询 OrderItem 中 orderId 在 orderIdList 中的元素
            LambdaQueryWrapper<OrderItem> orderItemLambdaQueryWrapper = new LambdaQueryWrapper<OrderItem>()
                    .in(OrderItem::getOrderId, orderIdList);
            // 调用持久层对象，传入 wrapper 条件构造器进行查询即可
            List<OrderItem> orderItemList = orderItemMapper.selectList(orderItemLambdaQueryWrapper);
            // 采用 stream 流，按 orderId 分组，分组后得到一个 Map 集合，Key 为 orderId
            // 例如：在 order_item 表中，只要 orderId 为 2 的所有商品 sku 明细都作为了一个集合存储在 key 为 2 的 map 的 value 中
            Map<Long, List<OrderItem>> orderItemListMap = orderItemList.stream()
                    .collect(Collectors.groupingBy(OrderItem::getOrderId));

            for (OrderInfo orderInfo: orderInfoList) {
                orderInfo.setOrderItemList(orderItemListMap.get(orderInfo.getId()));
            }
            return orderInfoList;
        }
        return null;
    }

    // 取消订单
    @Override
    public void cancelOrder(Long orderId) {
        // 根据订单 ID 查询订单信息
        OrderInfo orderInfo = this.getById(orderId);
        // 判断订单状态，如果是未支付状态，才可以取消
        if (orderInfo != null && orderInfo.getOrderStatus() == 0) {
            orderInfo.setOrderStatus(-1);
            // 根据实体类的主键去更新即可
            this.updateById(orderInfo);
            // 基于 RabbitMQ 去解锁库存
            // TODO：保存订单时，可用 RabbitMQ 发送一个延迟消息，如果用户 15 分钟内没有付款，则自动关闭订单
            rabbitService.sendMessage(
                    MqConst.EXCHANGE_PRODUCT, // EXCHANGE_PRODUCT = "spzx.exchange.product"
                    MqConst.ROUTING_UNLOCK, // ROUTING_UNLOCK = "spzx.routing.unlock";
                    orderInfo.getOrderNo());
        }
    }
}
